//----------------------------------------------------------------------------
//  ,-.
// /_\___ ,   Ninja Framework
// '  ___\    (C) Riccardo Capra - http://riccar.do
//  //   \\
//----------------------------------------------------------------------------
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
//----------------------------------------------------------------------------

#ifndef NJ_RECURSIVE_MUTEX_H
#	define NJ_RECURSIVE_MUTEX_H

#	include "NjNinjaSyncLib.h"
#	include "NjMutex.h"

	/// \brief Implements a recursive mutual exclusion synchronization object
	///
	/// Mutual exclusion synchronization objects are typically used to avoid that two threads concurrently use the same shared resource.
	/// Being recursive means that if the same thread locks the same mutex more than once, the thread will be not suspended. A counter will be incremented and the mutex will be released only once the original thread has called Unlock exactly the same amount of times Lock was called.
	/// NjMutex is similar to NjRecursiveMutex but it does not support recursion.
	/// The recursion support, comparted to NjMutex has an extra memory overhead of sizeof(NjUint) and further is more CPU expensive as it require to determine the calling thread id at each Lock or TryLock call.
	class NjRecursiveMutex
	{
	public:
		/// \brief Defines a scoped-lock for NjRecursiveMutex
		typedef NjLocker<NjRecursiveMutex> Locker;

		/// \brief Defines the timeout type
		typedef NjEventsSystem::TimeMs TimeMs;

		/// \brief Creates a NjRecursiveMutex instance
		NjRecursiveMutex();

		/// \brief Destroys this NjRecursiveMutex instance
		~NjRecursiveMutex();

		/// \brief Acquires the mutex and put it in locked state
		///
		/// At any time at maximum just one thread can acquire the mutex. The other threads will be suspended until the mutex is released by  an Unlock call
		void Lock();

		/// \brief Attempts to acquire the mutex and put it in locked state
		///
		/// TryLock(0) will attempt to acquire the mutex and will return immediately if the mutex is already locked, without waiting further.
		/// TryLock(NjEventsSystem::WAIT_FOREVER) is identical to Lock
		/// \param uTimeMs The time out in milliseconds
		/// \return If the mutex is acquired within the provided time out, TryLock will return NJ_TRUE otherwise it will return NJ_FALSE
		NjBool TryLock(TimeMs uTimeMs);

		/// \brief Releases a previously acquired mutex. Just the thread that initially successfully acquired the mutex can call `Unlock`. This condition is enforced in some build configurations through assertions. To fully release a mutex the locking thread should call `Unlock` exactly the same amount of times it previously called `Lock`
		void Unlock();

	private:
		/// \brief Step used to count recursion iterations
		static const NjUint RECURSION_COUNTER = 0x00000001;

		/// \var static const NjUint RECURSION_MASK
		/// \brief Mask where the recursion counter is stored

#ifdef NJ_PLATFORM_WIN64
		static const NjUint RECURSION_MASK    = 0x00000000FFFFFFFFULL;
#else // NJ_PLATFORM_WIN64
		static const NjUint RECURSION_MASK    = 0x0000FFFF;
#endif // NJ_PLATFORM_WIN64

		/// \brief Step used to count an unique thread id
		static const NjUint THREAD_ID_COUNTER = RECURSION_MASK+1;

		/// \brief Mask where the unique thread id is stored
		static const NjUint THREAD_ID_MASK    = ~RECURSION_MASK;

		/// \brief Invalid thread id value
		static const NjUint INVALID_THREAD_ID = 0x00000000;

		/// \brief Private class for internal use
		class Static;

		/// \brief The underlying mutex
		NjMutex m_kMutex;

		/// \brief Stores the state related to recursion
		NjUint m_uRecursionData;

		NJ_DISABLE_COPY(NjRecursiveMutex);
	};

#	include "NjRecursiveMutex_inline.h"

#endif // NJ_RECURSIVE_MUTEX_H
